#pragma once

#include <type_traits>
#include <algorithm>

#include "base.h"
#include "../updater/ma_deriv.h"

namespace mdtx::online::indicator
{
    /**
     * @brief Wrap an MA indicator to MACD indicator
     *
     * @tparam MaImpl an MA indicator
     */
    template <typename MaImpl>
    class macd_wrapper : public indi_base<typename MaImpl::scala_in,
                                          typename MaImpl::scala_out, // updater::macd<T>::scala_out === T
                                          MaImpl::num_in, 3>
    {
        using base = indi_base<typename MaImpl::scala_in,
                               typename MaImpl::scala_out,
                               MaImpl::num_in, 3>;
        using ma_indi_type = std::enable_if_t<MaImpl::num_out == 1, MaImpl>;

    public:
        using typename base::scala_in;
        using typename base::scala_out;
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        ptrdiff_t n_fast, n_slow, n{};
        ma_indi_type ma_fast, ma_slow;
        updater::macd<scala_out> updater;

    public:
        template <typename... Args>
        explicit macd_wrapper(ptrdiff_t fast_period = 12, ptrdiff_t slow_period = 26, ptrdiff_t sig_period = 9, Args... args)
            : n_fast(std::min(fast_period, slow_period)),
              n_slow(std::max(fast_period, slow_period)),
              ma_fast(n_fast, args...),
              ma_slow(n_slow, args...),
              updater(scala_in(2) / (sig_period + 1))
        {
        }

        /**
         * @brief update
         *
         * @param in passed to fast and slow MA indicator
         * @return tuple_out MACD, signal line, histogram
         */
        tuple_out update(tuple_in const &in) override
        {
            auto [mfast] = ma_fast.update(in);
            auto [mslow] = ma_slow.update(in);
            if (n == n_slow)
            {
                updater.insert_impl(mfast, mslow);
            }
            else
            {
                n += 1;
            }
            return updater.get();
        }
    };

    /**
     * @brief Wrap a baseline indicator to RSI indicator
     *
     * @tparam BlImpl a baseline indicator
     */
    template <typename BlImpl>
    class rsi_wrapper : public indi_base<typename BlImpl::scala_in,
                                         typename BlImpl::scala_out, // updater::rsi<T>::scala_out === T
                                         BlImpl::num_in, 1>
    {
        using base = indi_base<typename BlImpl::scala_in,
                               typename BlImpl::scala_out,
                               BlImpl::num_in, 1>;
        using bl_indi_type = std::enable_if_t<BlImpl::num_out == 1, BlImpl>;

    public:
        using typename base::scala_in;
        using typename base::scala_out;
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::update;

    protected:
        bl_indi_type bl;
        updater::rsi<scala_out> updater;

    public:
        template <typename... Args>
        explicit rsi_wrapper(ptrdiff_t sig_period, Args... args)
            : bl(args...),
              updater(scala_in(2) / (sig_period + 1))
        {
        }

        /**
         * @brief update
         *
         * @param in passed to baseline indicator
         * @return tuple_out rsi
         */
        tuple_out update(tuple_in const &in) override
        {
            auto baseline = bl.update(in);
            updater.insert_impl(std::get<0>(in), std::get<0>(baseline));
            return updater.get();
        }
    };
}
